Skip to content

Conversation

@JonathanOppenheimer
Copy link
Member

@JonathanOppenheimer JonathanOppenheimer commented Nov 21, 2025

Why this should be merged

This PR accomplishes three things:

  1. Prevent files inside of the vms/evm directory from importing packages within the graft/ directory.
    • This is important because with the removal of coreth, there is not longer a natural boundary between the uplifted and non-uplifted code. We need to enforce such a boundary.
    • This boundary is important, especially as other people individuals start work on properly uplfiting code post monorepo. Reviewers and developers may not be familiar with why we want this distinction to exist and programmatically enforcing this boundary will prevent mistakes and leakage of legacy EVM code into AvalancheGo.
  2. Prevent libevm/pseudo from being imported anywhere in AvalancheGo

How this works

The boundaries are as follows:

  • graft/coreth can be imported everywhere in AvalancheGo except vms/evm.
    • However, vms/evm/emulate is an exception to this rule
  • graft/subnet-evm can not be imported anywhere in AvalancheGo
    • However, vms/evm/emulate is an exception to this rule

How this was tested

Existing CI. I also added a sample "bad" commit (404acd5) to show how CI would behave.
Screenshot 2025-11-21 at 6 34 17 PM

edit: this is no longer a good example and is out of date but I kept it to show the formatting -- pretend the file is in vms/evm/sample_bad.go.

Need to be documented in RELEASES.md?

No

@JonathanOppenheimer JonathanOppenheimer self-assigned this Nov 21, 2025
@JonathanOppenheimer JonathanOppenheimer added the testing This primarily focuses on testing label Nov 21, 2025
@JonathanOppenheimer JonathanOppenheimer marked this pull request as ready for review November 21, 2025 23:37
@JonathanOppenheimer JonathanOppenheimer marked this pull request as ready for review November 24, 2025 23:06
isInEmulate := strings.Contains(path, "/vms/evm/emulate/")
isInVmsEvm := strings.Contains(path, "/vms/evm/")
isCoreth := strings.Contains(importPath, "/graft/coreth")
isSubnetEVM := strings.Contains(importPath, "/graft/subnet-evm")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(No action required) Not a biggie, but would prefer to see subnet-evm specific stuff as part of the migration not in advance.

But also, why is testing for these independent grafts conflated? Maybe implement 2 tests that use the same helpers, one for each graft location?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ARR4N suggested that all of this be combined into a single import violation test. Would you still like to see the tests separated out? I see the benefit of both ways

  • single test: less code, everything in one place
  • two tests: separation makes more sense semantically

I could also do a single test with two subtests?

require.Fail(t, formatImportViolations(foundImports, header))
}

// TestDoNotImportLibevmPseudo ensures that no code in the repository imports
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(No action required) If it's libevm-internal only, why isn't the package called internal so that the restriction is enforced automatically by golang?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ARR4N would this be possible?

ARR4N
ARR4N previously requested changes Nov 25, 2025
Comment on lines 24 to 25
// - graft/coreth can be imported anywhere EXCEPT vms/evm (but vms/evm/emulate is an exception)
// - graft/subnet-evm cannot be imported anywhere EXCEPT vms/evm/emulate
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these the rules? I understand emulate being allowed to import, but not the other limitations.

Rule of thumb for all code, not just this instance: comments should explain "why", not "what" nor "how".

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a longer comment explaining why - is it satisfactory?

@github-project-automation github-project-automation bot moved this to In Progress 🏗️ in avalanchego Nov 25, 2025
@ARR4N
Copy link
Contributor

ARR4N commented Nov 25, 2025

I think the entirety of this PR can be reduced to something like this:

func TestImportViolations(t *testing.T) {
	const root ".."
	err := filepath.Walk(root, func(file string, info fs.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if strings.ToLower(filepath.Ext(file)) != "go" {
			return nil
		}

		node, err := parser.ParseFile(token.NewFileSet(), file, nil, parser.ImportsOnly)
		if err != nil {
			return fmt.Errorf("parser.ParseFile(..., %q, ...): %v", file, err)
		}

		for _, spec := range node.Imports {
			if spec.Path == nil {
				continue
			}
			imp := strings.Trim(spec.Path.Value, `"`)

			inGraft := strings.Contains(file, "/graft/")
			inEmulate := strings.Contains(file, "/vms/evm/emulate/")
			inVMsEVM := strings.Contains(file, "/vms/evm/")
			importsPseudo := isPackageIn(imp, "github.com/ava-labs/libevm/libevm/pseudo")
			importsCoreth := isPackageIn(imp, "github.com/ava-labs/avalanchego/graft/coreth")
			importsSubnetEVM := isPackageIn(imp, "github.com/ava-labs/avalanchego/graft/subnet-evm")

			// TODO: before merging, this needs a comment explaining why each of them are a violation
			violations := []bool{
				importsPseudo,
				!inGraft && importsCoreth && inVMsEVM && !inEmulate,
				!inGraft && importsSubnetEVM && !inEmulate,
			}
			if slices.Contains(violations, true) {
				t.Errorf("File %q imports %q", file, imp)
			}
		}
		return nil
	})

	require.NoErrorf(t, err, "filepath.Walk(%q)", root)
}

func isPackageIn(importPath, root string) bool {
	if importPath == root {
		return true
	}
	if len(importPath) < len(root)+1 {
		return false
	}
	return strings.HasPrefix(importPath, root) && importPath[len(root)] == '/'
}

I'm not particularly happy with the relative path to walk nor the way of checking if something is in a sub-dir (they can be fixed together), but otherwise I think this covers all bases.

JonathanOppenheimer and others added 2 commits December 1, 2025 13:31
Co-authored-by: Arran Schlosberg <519948+ARR4N@users.noreply.github.com>
Signed-off-by: Jonathan Oppenheimer <147infiniti@gmail.com>
@JonathanOppenheimer
Copy link
Member Author

I'm not particularly happy with the relative path to walk nor the way of checking if something is in a sub-dir (they can be fixed together), but otherwise I think this covers all bases.

I feel bad that you put all of these great comments together and then just gave me a much simpler test, but your diligence is appreciated. I have replaced the file with your suggested test (tweaked slightly)

Copy link
Contributor

@ARR4N ARR4N left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GitHub is forcing me to add a comment here for no reason (well, no reason beyond that it's a terrible product).

// TODO(jonathanoppenheimer): remove the emulate functionality once the emulate package is removed.
// TODO(jonathanoppenheimer): remove the graft functionality once the graft package will be removed.
func TestImportViolations(t *testing.T) {
var violations []string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why add the layer of indirection? Just report the violation when it arises.

FWIW this is why forcing use of require instead of judicious use of assert/require as appropriate is a bad idea. If multiple violations occur then it forces whack-a-mole fixing and rerunning of the test every time. Strict adherence to a style guide is not a valid reason to write more complicated code as it's antithetical to the purpose of a guide.

Copy link
Contributor

@maru-ava maru-ava Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert was banned because in too many instances it was assumed to be equivalent to require (fail-fast) and this resulted in buggy tests. For those instances where multiple violations are possible, t.Log is always an option.

Copy link
Member Author

@JonathanOppenheimer JonathanOppenheimer Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also will catch all the violations no? Fill up the array first, and then check the array at the end -- so it won't have to be rerun a ton of times.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume he would prefer that the use of inline assertions so as to avoid the requirement to accumulate failures and output them at the end?

FWIW, I'm sympathetic to revisiting the assert ban, maybe something to bring up in retro? I would hope that we now have enough review maturity to avoid misuse going forward.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yah, I understand. If we'd like to revisiting the ban, that totally makes sense, as it would certainly enable additional testing patterns, but I don't think this is the PR to do that in 😂. For retro!

Comment on lines 67 to 69
inGraft := strings.Contains(file, "/graft")
inEmulate := strings.Contains(file, "/vms/evm/emulate")
inVMsEVM := strings.Contains(file, "/vms/evm")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

strings.Contains is subject to false positives. First find the repo root with filepath.Abs(root) and then check for prefixes with strings.HasPrefix(file, filepath.Join(repoRoot, "/graft")) etc.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've done so!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me know if this what you had in mind.

Comment on lines +101 to +103
// Check if importPath is a subpackage by ensuring it has the targetedImport prefix
// AND the next character is '/'. This is to prevent false positives where one
// package name is a prefix of another like "github.com/foo" vs "github.com/foobar".
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Check if importPath is a subpackage by ensuring it has the targetedImport prefix
// AND the next character is '/'. This is to prevent false positives where one
// package name is a prefix of another like "github.com/foo" vs "github.com/foobar".

This just says what the code does so shouldn't be there as it's redundant and risks being or becoming incorrect. Heuristic: only explain "why" in inline comments, not how nor what. If a how or what is necessary then the code probably needs to be refactored.

More details.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the what is necessary as Maru requested an explanation of the intent behind the code here: #4537 (comment).

JonathanOppenheimer and others added 4 commits December 2, 2025 12:08
Co-authored-by: Arran Schlosberg <519948+ARR4N@users.noreply.github.com>
Signed-off-by: Jonathan Oppenheimer <147infiniti@gmail.com>
Co-authored-by: Arran Schlosberg <519948+ARR4N@users.noreply.github.com>
Signed-off-by: Jonathan Oppenheimer <147infiniti@gmail.com>
Co-authored-by: Arran Schlosberg <519948+ARR4N@users.noreply.github.com>
Signed-off-by: Jonathan Oppenheimer <147infiniti@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

monorepo testing This primarily focuses on testing

Projects

Status: In Progress 🏗️

Development

Successfully merging this pull request may close these issues.

4 participants